package file

import (
	"archive/zip"
	"bytes"
	"crypto/md5"
	"crypto/sha512"
	"encoding/base64"
	"errors"
	"fmt"
	"github.com/shirou/gopsutil/disk"
	"io"
	"io/ioutil"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strings"
)

// GetHomeRootPath 获得用户目录
// return 用户根目录
func GetHomeRootPath() string {
	currentUser, err := user.Current()
	if err != nil {
		return ""
	}
	return currentUser.HomeDir
}

// Exist 判断目录或文件是否存在
// inputPath 指定路径
// 返回true:文件或目录存在
// 返沪false:文件或目录不存在
func Exist(inputPath string) bool {
	if inputPath == "" {
		panic("input path is empty")
	}
	_, err := os.Stat(inputPath)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

// CreatePath 创建目录
// inputPath 指定路径
// return true:创建目录成功,false::创建目录失败
func CreatePath(inputPath string) error {
	if Exist(inputPath) {
		return nil
	}
	return os.MkdirAll(inputPath, os.ModePerm)
}

// CreateFile 创建文件
// inputPath 指定路径
// return error:
func CreateFile(inputPath string) error {
	if Exist(inputPath) {
		return nil
	}

	if parentPath := path.Dir(inputPath); parentPath != "" {
		if err := CreatePath(parentPath); err != nil {
			return err
		}
	}
	file, err := os.Create(inputPath)
	if nil != err {
		return err
	}
	if file == nil {
		return errors.New("create file failed")
	}
	return file.Close()
}

//Dir 获得输入路径的上层目录
func Dir(inputPath string) string {
	return path.Dir(inputPath)
}

//Rel 获得输入路径的相对路径
func Rel(inputPath, dir string) string {
	if rel, err := filepath.Rel(dir, inputPath); err == nil {
		return rel
	}
	return ""
}

// Remove 删除目录或文件
// inputPath 指定路径
// return true:删除成功,false::删除失败
func Remove(path string) bool {
	if !Exist(path) {
		fmt.Print(path)
		return false
	}
	err := os.RemoveAll(path)
	if nil != err {
		fmt.Print(err.Error())
		return false
	}
	return true
}

// OpenFile 打开文件
// inputPath 指定路径
// forWrite 是否可写
// 返回 file 文件指针，err，错误
func OpenFile(path string, create, forWrite bool) (file *os.File, err error) {
	if !Exist(path) {
		if create {
			if err := CreateFile(path); err != nil {
				return nil, err
			}
		} else {
			return nil, os.ErrNotExist
		}
	}
	if forWrite {
		return os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0666)
	}
	return os.OpenFile(path, os.O_RDONLY|os.O_APPEND, 0666)
}

// ReadToB64 读取文件，输出为base64
// inputPath 指定路径
// @return base64格式的数据内容
func ReadToB64(path string) (string, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(b), nil
}

// WriteFromB64 向指定文件写入内容
// inputPath 指定路径
// data 数据内容
// @return 写入结果错误码
func WriteFromB64(inputPath, data string) error {
	b, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		return err
	}
	parentPath := path.Dir(inputPath)
	if !Exist(parentPath) {
		if creatPathErr := CreatePath(parentPath); creatPathErr != nil {
			return creatPathErr
		}
	}
	return ioutil.WriteFile(inputPath, b, 0666)
}

// Write 向指定文件写入内容
// inputPath 指定路径
// data 数据内容
// @return 写入结果错误码
func Write(inputPath string, data []byte) error {
	parentPath := path.Dir(inputPath)
	if !Exist(parentPath) {
		if createPathErr := CreatePath(parentPath); createPathErr != nil {
			return createPathErr
		}
	}
	return ioutil.WriteFile(inputPath, data, 0666)
}

// Read 读取指定文件的内容
// inputPath 指定路径
// @return 数据内容和错误码
func Read(inputPath string) (string, error) {
	d, err := ioutil.ReadFile(inputPath)
	return string(d), err
}

// ReadByte 读取指定文件的内容
// inputPath 指定路径
// @return 数据内容和错误码
func ReadByte(inputPath string) ([]byte, error) {
	d, err := ioutil.ReadFile(inputPath)
	return d, err
}

// Size 获得文件大小,单位KB
func Size(inputPath string) int64 {
	var result int64 = 0
	if !Exist(inputPath) {
		return result
	}
	f, err := os.Stat(inputPath)
	if err != nil {
		return result
	}
	return f.Size() >> 10
}

// MD5 计算文件16位md5
func MD5(inputPath string) (string, error) {
	d, err := ioutil.ReadFile(inputPath)
	if err != nil {
		return "", err
	}
	result := md5.Sum(d)
	return fmt.Sprintf("%x", result), nil
}

// MD5 计算文件16位md5
func MustMD5(inputPath string) string {
	d, err := ioutil.ReadFile(inputPath)
	if err != nil {
		panic("read file error:" + err.Error())
	}
	result := md5.Sum(d)
	return fmt.Sprintf("%x", result)
}

// MD5FromByte 计算文件16位md5
func MD5FromByte(data []byte) string {
	result := md5.Sum(data)
	return fmt.Sprintf("%x", result)
}

// MD5FromString 计算文件16位md5
func MD5FromString(data string) string {
	result := md5.Sum([]byte(data))
	return fmt.Sprintf("%x", result)
}

// Sha512 计算输入的sha512值
func Sha512(raw string) string {
	result := sha512.Sum512([]byte(raw))
	return fmt.Sprintf("%x", result)
}

// DiskFreeCheck 获得磁盘剩余空间大小
func DiskFreeCheck() uint64 {
	use, _ := disk.Usage(GetHomeRootPath())
	return use.Free >> 20
}

// ErgodicFile 遍历指定目录中的文件
// inputPath 需要遍历的目录
// return fileList 符合要求的文件列表, err 错误
func ErgodicFile(inputPath string) (fileList []string, err error) {
	var result []string
	files, err := ioutil.ReadDir(inputPath)
	if nil != err {
		return result, err
	}
	for _, fi := range files {
		if fi.IsDir() {
			continue
		}
		result = append(result, fi.Name())
	}
	return result, nil
}

// RecursiveErgodicFile 遍历指定目录中的文件
// inputPath 需要遍历的目录
// return fileListDir 符合要求的文件列表(包含目录), err 错误
func RecursiveErgodicFile(inputPath string) (fileListDir []string, err error) {
	var result []string
	files, err := ioutil.ReadDir(inputPath)
	if nil != err {
		return result, err
	}
	for _, fi := range files {
		if fi.IsDir() {
			fl, errR := RecursiveErgodicFile(inputPath + "/" + fi.Name())
			if errR != nil {
				return result, errR
			}
			result = append(result, fl...)
			continue
		}
		result = append(result, inputPath+"/"+fi.Name())
	}
	return result, nil
}

// ErgodicExtFile 遍历指定目录中指定后缀的文件
// inputPath 需要遍历的目录
// ext 后缀名,例如“txt"
// return fileList 符合要求的文件列表, err 错误
func ErgodicExtFile(inputPath string, ext string) (fileList []os.FileInfo, err error) {
	var result []os.FileInfo
	files, err := ioutil.ReadDir(inputPath)
	if nil != err {
		return result, err
	}
	for _, fi := range files {
		if fi.IsDir() {
			continue
		}

		if ext == strings.TrimPrefix(path.Ext(fi.Name()), ".") {
			result = append(result, fi)
		}
	}
	return result, nil
}

type File struct {
	AbsolutePath string
	os.FileInfo
}

// RecursiveErgodicFile 遍历指定目录中的文件
// inputPath 需要遍历的目录
// return fileListDir 符合要求的文件列表(包含目录), err 错误
func RecursiveErgodicExtFile(inputPath string, ext string) (fileListDir []File, err error) {
	var result []File
	files, err := ioutil.ReadDir(inputPath)
	if nil != err {
		return result, err
	}
	for _, fi := range files {
		if fi.IsDir() {
			fl, errR := RecursiveErgodicExtFile(path.Join(inputPath, fi.Name()), ext)
			if errR != nil {
				return result, errR
			}
			result = append(result, fl...)
			continue
		}
		if ext == strings.TrimPrefix(path.Ext(fi.Name()), ".") {
			result = append(result, File{path.Join(inputPath, fi.Name()), fi})
		}
	}
	return result, nil
}

// ErgodicDir 遍历指定目录中所有的文件夹
// inputPath 需要遍历的目录
// return dirList 该目录下所有的文件夹, err 错误
func ErgodicDir(inputPath string) (dirList []string, err error) {
	var result []string
	files, err := ioutil.ReadDir(inputPath)
	if nil != err {
		return result, err
	}
	for _, fi := range files {
		if fi.IsDir() {
			result = append(result, fi.Name())
		}
	}
	return result, nil
}

type DataFile interface {
	Path() string //file relative path
	Data() []byte //file data
	io.Reader
}

func Zip(files []DataFile) io.Reader {
	if files == nil {
		panic("zip failed,files is nil")
	}
	b := &bytes.Buffer{}
	archive := zip.NewWriter(b)

	for _, item := range files {
		zf, _ := archive.Create(item.Path())
		zf.Write(item.Data())
	}
	archive.Close()
	return b
}
